/*-
 * CH.java
 *
 * $LastChangedBy: stein $
 * $LastChangedRevision: 95 $
 * $LastChangedDate: 2007-02-13 16:19:24 +0000 (Tue, 13 Feb 2007) $
 * $HeadURL: svn://localhost/SMS/src/com/googlecode/attention/sms/comp/CH.java $
 *
 * Copyright (c) 2007, Stein Gunnar Bakkeby
 * All rights reserved.
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of Stein Gunnar Bakkeby nor the names of its
 *       contributors may be used to endorse or promote products derived
 *       from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS "AS IS" AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
package com.googlecode.attention.sms.comp;

import com.googlecode.attention.util.IntArrayBuilder;

import static com.googlecode.attention.util.bit.ByteBitValues.*;

/**
 * Compression Header (CH), used when compressing / decompressing user data.
 * <BR>See 3GPP TS 23.042 Version 5.0.0 Release 5
 *
 * @author Stein Gunnar Bakkeby
 */
public class CH extends IntArrayBuilder {

  private static final long serialVersionUID = 882636063464140969L;

  /* Static field values */
  /**
   * Extend CLC. The CH Extension Value contains higher order bits that
   * are to be pre-pended to the current CLC value.
   */
  public static final int EXTEND_CLC = 0;
  /**
   * Change Character Set. The CLC defines a default character set
   * (UCS2 or otherwise) within which compression will operate.
   * The Change Character Set CH Extension Type indicates that this should be
   * overridden by the character set specified by the CH Extension Value.
   * If a CH contains more than one Change Character Set CH Extension Type
   * octet, the CH Extension Value contained in subsequent CH octets of this
   * type contains higher order bits and are to be pre-pended to the value
   * of the new character set.
   * <BR>
   * <BR>The following Character Sets are defined:
   * <BR>
   * <BR>0000 No character set defined. To be used where original message
   *          content is binary data and compression is solely via Huffman
   *          coding with no initial frequency training and thus there is no
   *          requirement to ensure consistent use of character set by coder
   *          and decoder.
   * <BR>0001 GSM 7 bit default alphabet (3GPP TS 23.038)
   * <BR>0010 Codepage 437
   * <BR>0011 Codepage 850
   * <BR>All other values are reserved
   */
  public static final int CHANGE_CHARACTER_SET = 1;
  /**
   * Change UCS2 Row. The CLC defines a default character set (UCS2 or
   * otherwise) within which compression will operate. The Change UCS2
   * Row CH Extension Type indicates that this should be overridden by
   * the use of UCS2 and the UCS2 row value for the first character in
   * the input stream is that specified by the CH Extension Value. If a
   * CH contains more than one Change UCS2 Row CH Extension Type octet,
   * the CH Extension Value contained in subsequent CH octets of this
   * type contains higher order bits for the initial UCS2 Row value and
   * are to be pre-pended to the current value.
   * <BR>
   * <BR>NOTE: Change UCS2 Row CH Extension Type octet effectively overrides
   * any prior Change Character Set CH Extension Type octet and vice versa
   * so these types are logically mutually exclusive within a given CH.
   */
  public static final int CHANGE_UCS2_ROW = 2;
  /**
   * Change Huffman Initialization. The CLC defines a default set of
   * parameters for the initialization of the Huffman (de)coder. The Change
   * Huffman Initialization CH Extension Type indicates that this should be
   * overridden by the set of initialization parameters identified by the
   * Huffman Initialization ID contained in the CH Extension Value. If a CH
   * contains more than one Change Huffman Initialization CH Extension Type
   * octet, the CH Extension Value contained in subsequent CH octets of this
   * type contains higher order bits for the initial Huffman Initialization
   * ID value and are to be pre-pended to the current value.
   */
  public static final int CHANGE_HUFFMAN_INITIALISATION = 3;
  /**
   * Change Keyword Dictionary. The CLC defines a default set of parameters
   * for the initialization of the Keyword (de)coder. The Change Keyword
   * Dictionary CH Extension Type indicates that this should be overridden
   * by the set of initialization parameters identified by the Keyword
   * Dictionary ID contained in the CH Extension Value. If a CH contains more
   * than one Change Keyword Dictionary CH Extension Type octet, the CH
   * Extension Value contained in subsequent CH octets of this type contains
   * higher order bits for the initial Keyword Dictionary ID value and are
   * to be pre-pended to the current value.
   */
  public static final int CHANGE_KEYWORD_DICTIONARY = 4;
  /**
   * Change Punctuator. The CLC defines a default set of parameters for the
   * initialization of the punctuation (de)coder. The Change Punctuator CH
   * Extension Type indicates that this should be overridden by the set of
   * initialization parameters identified by the Punctuator ID contained in
   * the CH Extension Value. If a CH contains more than one Punctuator CH
   * Extension Type octet, the CH Extension Value contained in subsequent CH
   * octets of this type contains higher order bits for the initial Punctuator
   * ID value and are to be pre-pended to the current value.
   */
  public static final int CHANGE_PUNCTUATOR = 5;
  /**
   * Change Character Group. The CLC defines a default set of parameters for
   * the initialization of the Character Group (de)coder. The Change Character
   * Group CH Extension Type indicates that this should be overridden by the
   * set of initialization parameters identified by the Character Group ID
   * contained in the CH Extension Value. If a CH contains more than one
   * Change Character Group CH Extension Type octet, the CH Extension Value
   * contained in subsequent CH octets of this type contains higher order
   * bits for the initial Character Group ID value and are to be pre-pended
   * to the current value.
   */
  public static final int CHANGE_CHARACTER_GROUP = 6;
  /**
   * Reserved. Any currently undefined values in the range 0 to 255 decimal
   * are reserved.
   * <BR>
   * <BR>Values above 255 are available for user to user requirements.
   */
  public static final int RESERVED = 7;
  /* End static field values */
  
  /* Field values */
  private int[] extValue = new int[8];
  private int[] extCount = new int[8];
    
  /**
   * Constructor, creates a default Compression Header that indicates
   * compression of an undefined language and not using punctuation, keyword,
   * or character groups.
   */
  public CH() {
    buffer = new int[5];
    buffer[0] = RANGE_3_TO_6; // 01111000
    endpoint = 1;
  }
  
  /**
   * Constructor, creates a clone of the given CH object.
   * 
   * @param ch The other compression header
   */
  public CH(final CH ch) {
    this(ch.toArray());
  }
  
  /**
   * Constructor, creates the Compression Header.
   * <BR>The first octet is mandatory.
   * 
   * @param octets The data that defines the compression
   * @throws IllegalArgumentException If input is not recognised
   */
  public CH(final int[] octets) {
    if (octets.length < 1) {
      throw new IllegalArgumentException(
          "First octet of compression header is mandatory!");
    }
    append(octets);
    extValue[EXTEND_CLC] = (buffer[begpoint] & RANGE_3_TO_6) >> 3;
    extCount[EXTEND_CLC] = 1;
    final int length = endpoint;
    for (int i = begpoint + 1; i < length; i++) {
      final int extType = (buffer[i] & RANGE_4_TO_6) >> 4;
      final int extVal  =  buffer[i] & RANGE_0_TO_3;
      extValue[extType] |= (extVal << (extCount[extType] * 4));
      extCount[extType] = extCount[extType] + 1;
      if (extCount[extType] > 3) throw new IllegalArgumentException();
    }
  }
  
  /**
   * This indicates whether the compression algorithm should be initialised
   * with other parameters as indicated by extension octets to the CH.
   * 
   * @return True if extension octets are present, false otherwise
   */
  public boolean hasExtensionOctets() {
    return (buffer[begpoint] & B7) != 0; // 0x80
  }
  
  /**
   * This will set the extension octets indicator, note that if this is set
   * to false it will also remove any existing extension octets.
   * 
   * @param value True if extension octets exists, false otherwise
   */
  public void setHasExtensionOctets(final boolean value) {
    if (value) buffer[begpoint] |= B7; // 0x80
    else {     buffer[begpoint] &= N7; // B6-B0
               endpoint = 1;
               extValue = new int[8];
               extCount = new int[8];
    }
  }
  
  /**
   * This will add an extension octet to the CH. 
   * 
   * @param octet The extension octet to add
   */
  private void addExtensionOctet(final int octet) {
    if ((buffer[begpoint] & B7) == 0) setHasExtensionOctets(true);
    if (endpoint > 0) buffer[endpoint - 1] |= B7; // 0x80
    append(octet);
  }
  
  /**
   * Use this to indicate whether an initialisation ID
   * needs to be changed and the value to change to.
   * 
   * @param type See static field values
   * @param value The value to change to
   */
  public void change(int type, int value) {
    if (type < 0 || type > RESERVED) type = RESERVED;
    if (value < 0) value = 0;

    removeExtensionOctets(type);
    
    if (type == EXTEND_CLC) {
      // TODO really verify this and write junit tests
      buffer[begpoint] ^= (buffer[begpoint] & RANGE_3_TO_6); // (B6|B5|B4|B3)
      buffer[begpoint] |= (value & RANGE_0_TO_3) << 3; // (B3|B2|B1|B0)
      extValue[EXTEND_CLC] = value & RANGE_3_TO_6; // (B6|B5|B4|B3)
      extCount[EXTEND_CLC] = 1;
      if (value <= 15) return;
      value = value >> 4;
    }

    final int t = type << 4;
    while (value > 15) {
      final int newValue = value & RANGE_3_TO_6; // (B3|B2|B1|B0)
      value = value >> 4;
      addExtensionOctet(t | newValue);
      extValue[type] |= (newValue << (extCount[type]++ * 4));
    }
    addExtensionOctet(t | value);
    extValue[type] |= (value << (extCount[type]++ * 4));
  }
  
  /**
   * This will remove all extension octets of a given extension type.
   * 
   * @param type See static field values
   */
  private void removeExtensionOctets(final int type) {
    if (extCount[type] > 0) {
      for (int i = begpoint + 1; i < endpoint; i++) {
        if (((buffer[i] & 0x70) >> 4) == type) { // (B6|B5|B4)
          remove(i--);
          extCount[type]--;
        }
      }
    }
  }
  
  /**
   * This will return if initialisation information has to be
   * updated or not.
   * 
   * @param another Another CH to compare with
   * @param type The type of extension value that is of interest
   * @return True if update is needed, false otherwise
   */
  public boolean isUpdateNeeded(final CH another, final int type) {
    boolean changeNeeded = false;
    
    if (this.getCLC() != another.getCLC()) changeNeeded = true;
    switch (type) {
      case CHANGE_HUFFMAN_INITIALISATION:
        if (this.isKeywordEnabled()        != another.isKeywordEnabled()
         || this.isCharacterGroupEnabled() != another.isCharacterGroupEnabled()
         || this.isPunctuationEnabled()    != another.isPunctuationEnabled()) {
             changeNeeded = true;
        }
        break;
      
      case CHANGE_KEYWORD_DICTIONARY:
        if (this.isKeywordEnabled()
            != another.isKeywordEnabled()) changeNeeded = true;
        break;
        
      case CHANGE_PUNCTUATOR:
        if (this.isPunctuationEnabled()
            != another.isPunctuationEnabled()) changeNeeded = true;
        break;
        
      case CHANGE_CHARACTER_GROUP:
        if (this.isCharacterGroupEnabled()
            != another.isCharacterGroupEnabled()) changeNeeded = true;
        break;
    }
    if (!changeNeeded) {
      if (this.getExtensionValue(CH.CHANGE_CHARACTER_SET)
       != another.getExtensionValue(CH.CHANGE_CHARACTER_SET)
       || this.getExtensionValue(type) != another.getExtensionValue(type)) {
        changeNeeded = true;
      }
    }
    return changeNeeded;
  }
  
  /**
   * This indicates whether the punctuation processor should be enabled or not.
   * 
   * @return True if punctuation processor is to be enabled, false otherwise
   */
  public boolean isPunctuationEnabled() {
    return (buffer[begpoint] & B2) != 0;
  }
  
  /**
   * Set this to true to indicate that punctuation processor should be used.
   * 
   * @param value True if punctuation processor is enabled, false otherwise
   */
  public void setPunctuationEnabled(final boolean value) {
    if (value) buffer[begpoint] |= B2; // 0x4
    else       buffer[begpoint] &= N2; // 0xFB
  }
  
  /**
   * This indicates whether the keyword processor should be enabled or not.
   * 
   * @return True if keyword processor is to be enabled, false otherwise
   */
  public boolean isKeywordEnabled() {
    return (buffer[begpoint] & B1) != 0; // 0x2
  }

  /**
   * Set this to true to indicate that keyword processor should be used.
   * 
   * @param value True if keyword processor is enabled, false otherwise
   */
  public void setKeywordEnabled(final boolean value) {
    if (value) buffer[begpoint] |= B1;
    else       buffer[begpoint] &= N1;
  }
  
  /**
   * This indicates whether character groups are enabled or not.
   * 
   * @return True if character groups are enabled, false otherwise
   */
  public boolean isCharacterGroupEnabled() {
    return (buffer[begpoint] & B0) != 0;
  }
  
  /**
   * Set this to true to indicate that character groups are used.
   * 
   * @param value True if character groups are enabled, false otherwise
   */
  public void setCharacterGroupEnabled(final boolean value) {
    if (value) buffer[begpoint] |= B0;
    else       buffer[begpoint] &= N0;
  }

  /**
   * This will return the Compression Language Context (CLC).
   * 
   * @return The CLC
   * @see com.googlecode.attention.sms.pdu.CBS
   */
  public int getCLC() {
    return extValue[EXTEND_CLC];
  }
  
  /**
   * This will set the Compression Language Context (CLC).
   * 
   * @param value The CLC value
   * @see com.googlecode.attention.sms.pdu.CBS
   */
  public void setCLC(final int value) {
    change(EXTEND_CLC, value);
  }
  
  /**
   * This will return an array (length 8) of extension values.
   * 
   * @return The extension values
   */
  public int[] getExtensionValues() {
    return extValue;
  }
  
  /**
   * This will return an array (length 8) which indicates the amount
   * of extension octets for each extension type.
   * 
   * @return The extension count
   */
  public int[] getExtensionCount() {
    return extCount;
  }
  
  /**
   * This will return an extension value for a given extension type.
   * A value of -1 indicates that no such extension octet of the given
   * type exists.
   * 
   * @param type See static field values
   * @return The value of the given extension type
   */
  public int getExtensionValue(final int type){
    if (type < 0 || type > RESERVED) return -1;
    if (extCount[type] == 0)  return -1;
    return extValue[type];
  }
}